#include "ClassifierRank.h"

LcHistogram::LcHistogram()
{
	set(LC_LAB,4,4,4);
}

void LcHistogram::work(Mat & src, Mat & hist)
{
	int bins[] = {n1,n2,n3};
    if(src.channels()!=3) exit(1);
    Mat tmp;
    src.copyTo(tmp);
    Mat hsv;

	int code;
	if(color_type==LC_RGB) code = CV_BGR2RGB;
	else if(color_type==LC_HSV) code = CV_BGR2HSV_FULL;
	else if(color_type==LC_LAB) code = CV_BGR2Lab;

    cvtColor(src,hsv,code);

    int histSize[] = {bins[0], bins[1], bins[2]};
    Mat his;
    his.create(3, histSize, CV_32F);
    his = Scalar(0);   
    CV_Assert(hsv.type() == CV_8UC3);
    MatConstIterator_<Vec3b> it = hsv.begin<Vec3b>();
    MatConstIterator_<Vec3b> it_end = hsv.end<Vec3b>();
    for( ; it != it_end; ++it )
    {
        const Vec3b& pix = *it;
        his.at<float>(pix[0]*bins[0]/256, pix[1]*bins[1]/256,pix[2]*bins[2]/256) += 1.f;
    }
	
    // ==== Remove small values ==== //
    float minProb = 0.01f;
    minProb *= hsv.rows*hsv.cols;
    Mat plane;
    const Mat *_his = &his;
	
    NAryMatIterator itt = NAryMatIterator(&_his, &plane, 1);   
    threshold(itt.planes[0], itt.planes[0], minProb, 0, THRESH_TOZERO);
    double s = sum(itt.planes[0])[0];
	
    // ==== Normalize ==== //
    s = 1./s * 255.;
    itt.planes[0] *= s;
	
    itt.planes[0].copyTo(hist);
}

void LcHistogram::set( ColorSpaceType n_color_type,
				int n_n1,
				int n_n2,
				int n_n3)
{
	n1 = n_n1; n2 = n_n2; n3 = n_n3;
	color_type = n_color_type;
	dim = n1 * n2 * n3;
}

//==============================================

void ClassifierRank::EMcluster( Mat & specturm, std::vector<int> & lb,int cluster_n)
{
	specturm = specturm.t();

	int n = specturm.rows;

	lb.clear();
	lb.resize(n);

	cv::EM gmm_model = EM::EM( cluster_n );

	Mat lab_mat;
	Mat class_probs;

	//train GMM using EM in OpenCV library
	gmm_model.train( specturm , cv::noArray() , lab_mat,class_probs);

	// trans opencv mat into vector
	for(int i = 0;i<n;i++) lb[i] = lab_mat.at<int>(i);

	specturm = specturm.t();
}

Mat ClassifierRank::getLaplacian( Mat & D)
{
	int m = 4;

	int n = D.rows;

	Mat W;

	cv::exp( -D, W);

	Mat diag_D;

	cv::reduce( W , diag_D ,0,CV_REDUCE_SUM);

	W = -W;

	for(int i = 0;i<n;i++) W.at<float>(i,i) += diag_D.at<float>(i);

	for(int i = 0;i<n;i++) diag_D.at<float>(i) = 1.0f / sqrt( diag_D.at<float>(i));

	for(int i = 0;i<n;i++)
	{
		float v = diag_D.at<float>(i);
		for(int j = 0;j<n;j++) W.at<float>(i,j)*=v;
	}

	for(int i = 0;i<n;i++)
	{
		float v = diag_D.at<float>(i);
		for(int j = 0;j<n;j++) W.at<float>(j,i)*=v;
	}

	Mat eigen_value, eigen_vector;
	cv::eigen( W, eigen_value,eigen_vector);

	Mat ans;

	eigen_vector.rowRange(n-m,n).copyTo(ans);

	return ans;
}

void Kmeans::cluster()
{
	Mat D_hist;
	getGlobalFeatAffine( D_hist );

	Mat specturm;

	retrival_feature.copyTo(specturm);

	//EMcluster( specturm, cluster_lab , cluster_n);

	int attempts = 10;
	TermCriteria crit(TermCriteria::MAX_ITER +TermCriteria::EPS,30,0.01);
	Mat centers;
	Mat kmeans_labs;
	kmeans(specturm,cluster_n,kmeans_labs,crit,attempts,KMEANS_PP_CENTERS);

	cluster_lab.resize( specturm.rows );
	for(int i = 0; i< specturm.rows;i++) cluster_lab[i] = kmeans_labs.at<int>(i);

	vector<vector<int> > frm_ids; frm_ids.resize(cluster_n);
	


	//vector< LcVideoReadVec > sub_readers; sub_readers.resize(cluster_n);
	
	for(int i = 0 ; i < cluster_n;i++ )
		{ frm_ids[i].clear(); frm_ids[i].reserve( D_hist.rows*5/cluster_n );}

	for(int j = 0, frame_id = my_reader->f_start;
		j < (int)cluster_lab.size();
		frame_id += my_reader->f_rate,j++)
		{ frm_ids[ cluster_lab[j] ].push_back(frame_id);}

	cluster_readers.resize( cluster_n );

	{
		int j = 0;
		for(int i = 0; i < cluster_n; i++)
			if( frm_ids[i].size() != 0)
			{
				if(i!=j) frm_ids[j] = frm_ids[i];
				j++;
			}
		frm_ids.resize( j);
		cout << j << " cluster actually get" << endl;
		cluster_n = j;
	}

	for(int i = 0; i <cluster_n;i++){
		cluster_readers[i] = new LcVideoReadVec(*my_reader);
		cluster_readers[i]->set( frm_ids[i] );	 
	}

	if(0){
		for(int i = 0 ; i < cluster_n;i++ )
		{
			for(int j = 0 ; j < int( frm_ids[i].size());j++)
				cout << frm_ids[i][j] << " " ;
			cout << endl;
			system("pause");
		}
	}

	if(0){
		for(int i = 0 ; i < cluster_n;i++ )
		{
			cv::destroyAllWindows();

			if( frm_ids[i].size()==0) cout << "class " << i << " with no frame" << endl;

			cluster_readers[i]->reset();

			for(int j = 0 ; j < int( frm_ids[i].size());j++)
			{
				
				Mat frm;
				int status = cluster_readers[i]->readNext( frm );

				if(status!=0) break;

				Mat dsp; cv::resize(frm,dsp,Size(),.5,.5);

				char fname[400];
				sprintf(fname,"debug_%d",j);
				imshow(fname, dsp);
				cv::waitKey(200);
			}
			cv::waitKey(0);
		}
	}


}

void ClassifierRank::cluster()
{
	
	Mat D_hist, D_classifier;

	getClassifierAffine( D_classifier );

	cout << D_classifier.rows << " by " << D_classifier.cols << endl;

	getGlobalFeatAffine( D_hist );

	Mat D = D_hist + D_classifier;

	Mat specturm = getLaplacian( D );

	if(0){
		Mat dsp;
		cv::resize( specturm, dsp, Size(600,600));
		lc::colorshow("debug",dsp);
		cv::waitKey(1);
	}

	EMcluster( specturm, cluster_lab , cluster_n);

	vector<vector<int> > frm_ids; frm_ids.resize(cluster_n);


	//vector< LcVideoReadVec > sub_readers; sub_readers.resize(cluster_n);
	
	for(int i = 0 ; i < cluster_n;i++ )
		{ frm_ids[i].clear(); frm_ids[i].reserve( D.rows*5/cluster_n );}

	for(int j = 0, frame_id = my_reader->f_start;
		j < (int)cluster_lab.size();
		frame_id += my_reader->f_rate,j++)
		{ frm_ids[ cluster_lab[j] ].push_back(frame_id);}

	cluster_readers.resize( cluster_n );

	{
		int j = 0;
		for(int i = 0; i < cluster_n; i++)
			if( frm_ids[i].size() != 0)
			{
				if(i!=j) frm_ids[j] = frm_ids[i];
				j++;
			}
		frm_ids.resize( j);
		cout << j << " cluster actually get" << endl;
		cluster_n = j;
	}

	for(int i = 0; i <cluster_n;i++){
		cluster_readers[i] = new LcVideoReadVec(*my_reader);
		cluster_readers[i]->set( frm_ids[i] );	 
	}

	if(0){
		for(int i = 0 ; i < cluster_n;i++ )
		{
			for(int j = 0 ; j < int( frm_ids[i].size());j++)
				cout << frm_ids[i][j] << " " ;
			cout << endl;
			system("pause");
		}
	}

	if(0){
		for(int i = 0 ; i < cluster_n;i++ )
		{
			cv::destroyAllWindows();

			if( frm_ids[i].size()==0) cout << "class " << i << " with no frame" << endl;

			cluster_readers[i]->reset();

			for(int j = 0 ; j < int( frm_ids[i].size());j++)
			{
				
				Mat frm;
				int status = cluster_readers[i]->readNext( frm );

				if(status!=0) break;

				Mat dsp; cv::resize(frm,dsp,Size(),.5,.5);

				char fname[400];
				sprintf(fname,"debug_%d",j);
				imshow(fname, dsp);
				cv::waitKey(200);
			}
			cv::waitKey(0);
		}
	}

}

void ClassifierRank::getGlobalFeatAffine(Mat & D)
{
	Mat global_feat;
	extractGlobalFeature( global_feat );
	//lc::LcMat2Bin("temp.bin",global_feat);
	//lc::LcBin2Mat("temp.bin",global_feat);

	int _n = global_feat.rows;

	cout << _n << endl;

	D = Mat::zeros( _n, _n , 5);

	for(int i = 0;i<_n;i++)
		for(int j = i; j<_n;j++)
		{
			D.at<float>(i,j) = D.at<float>(j,i) 
				= (float) cv::norm( global_feat.row(i) - global_feat.row(j), NORM_L2);
		}

	float lambda_D = lc::reduceMat( D, CV_REDUCE_AVG) + 1e-3f;

	lambda_D = 1.0f / lambda_D ;

	D = D * lambda_D;

	global_feat.copyTo( retrival_feature );
}

void ClassifierRank::getClassifierAffine(Mat & D)
{
	string score_record_name;

	{
		score_record_name = my_reader->root + "classifier_output/train_per_frame/" 
			+ my_reader->video_name + "_" + feature_code_string + "_score_record.bin";
	}

	cout << score_record_name << endl;
	Mat score_record;
	
	//lc::LcBin2Mat("GOPR2384_cab_l_score_record.bin", score_record);
	lc::LcBin2Mat( score_record_name.c_str() ,score_record );

	int _n = score_record.rows;
 
	D = Mat::zeros( _n, _n , 5);

	for(int i = 0;i<_n;i++)
	{
		for(int j = 0,k = 0; k<_n;k++)
		{
			LcValidator temp;
			temp.tp = score_record.at<float>(i,j); j++;
			temp.fp = score_record.at<float>(i,j); j++;
			temp.fn = score_record.at<float>(i,j); j++;
			temp.tn = score_record.at<float>(i,j); j++;
			//D.at<float>(i,k) = temp.getF1();
			D.at<float>(i,k) = temp.getZeroOne()*.99f;
		}
	}

	D = max(D-0.5,0);
	D = 1.0 - D + D.t();

	float lambda_D = lc::reduceMat( D, CV_REDUCE_AVG) + 1e-3f;

	lambda_D = 1.0f / lambda_D ;

	D = D * lambda_D;
}

void ClassifierRank::extractGlobalFeature( Mat & global_desc )
{
	my_reader->reset();

	int _frame_n = my_reader->getN();

	int _dim = global_extractor->dim;
	
	global_desc = Mat::zeros( _frame_n, _dim, 5);

	cout << "[ " << my_reader->f_start << " : " << my_reader->f_rate << " : " 
		<< my_reader->f_end << " ]" << endl;

	for(int i = 0 ; i < 1e4; i++)
	{
		Mat frm, gt;

		Mat desc, lab;

		int statu = my_reader->readNext(frm);

		if(statu!=0) break;

		int frame_id = my_reader->getf();

		if(0){
			my_reader->getLabel( gt);

			Mat dsp;
			cvtColor(gt,dsp,CV_GRAY2BGR);

			addWeighted(frm,0.5,dsp,0.5,0, dsp);
			imshow("debug",dsp);cv::waitKey(0);
		}

		cout << "==========work with frame " <<
			frame_id << " ==============" << endl;

		{
			Mat temp_hist;
			global_extractor->work( frm, temp_hist );
			temp_hist.copyTo( global_desc.row(i) );

			if(0 && i%5==0){
				Mat dsp;
				cv::resize( global_desc, dsp, Size( 800,600));
				lc::colorshow("debug", dsp);
				cv::waitKey(1);
			}
		}
	}

	
}

void ClassifierRank::setGlobalExtractor( LcGlobalExtractor * n_global_extractor)
{
	global_extractor = n_global_extractor;
}

Mat ClassifierRank::getNormLaplacian( Mat & D )
{

	int m = 4;

	int n = D.rows;

	Mat W = Mat::zeros(n,n,5);

	//lc::colorshow("debug D" ,D);cv::waitKey(500);

	cv::exp( - D,W);

	Mat diag_D;

	cv::reduce( W , diag_D ,0,CV_REDUCE_SUM);

	W = -W;

	for(int i = 0;i<n;i++) W.at<float>(i,i) += diag_D.at<float>(i);

	for(int i = 0;i<n;i++) diag_D.at<float>(i) = 1.0f / sqrt( diag_D.at<float>(i));

	for(int i = 0;i<n;i++)
	{
		float v = diag_D.at<float>(i);
		for(int j = 0;j<n;j++) W.at<float>(i,j)*=v;
	}

	for(int i = 0;i<n;i++)
	{
		float v = diag_D.at<float>(i);
		for(int j = 0;j<n;j++) W.at<float>(j,i)*=v;
	}

	Mat eigen_value, eigen_vector;
	cv::eigen( W, eigen_value,eigen_vector);

	//for(int i = 0;i<n;i++) printf("%.2f ",eigen_value.at<float>(i));

	Mat ans;

	eigen_vector.rowRange(n-m,n).copyTo(ans);

	return ans.t();
}

void ClassifierRank::Laplacian()
{
	Mat score_record;
	lc::LcBin2Mat("GOPR2384_cab_l_score_record.bin", score_record);

	int _n = score_record.rows;

	Mat D = Mat::zeros( _n, _n , 5);

	for(int i = 0;i<_n;i++)
	{
		for(int j = 0,k = 0; k<_n;k++)
		{
			LcValidator temp;
			temp.tp = score_record.at<float>(i,j); j++;
			temp.fp = score_record.at<float>(i,j); j++;
			temp.fn = score_record.at<float>(i,j); j++;
			temp.tn = score_record.at<float>(i,j); j++;
			D.at<float>(i,k) = temp.getF1();
		}
	}
	D = D + D.t();

	float lambda_D = lc::reduceMat( D, CV_REDUCE_AVG) + 1e-3f;

	lambda_D = 1.0f / lambda_D ;

	D = D * lambda_D;

	Mat E = getNormLaplacian( D );

	cout << "size of E " << E.rows << " by " << E.cols << endl;

}

ClassifierRank::ClassifierRank()
{
	cluster_n = 20;
	veb = 1;
	feature_code_string = "";
	setFeatureCode( "l");
	setClassifier( new LcRandomTrees );
	setGlobalExtractor( new LcHistogram );
}

void ClassifierRank::setReader( LcVideoReadExt * n_reader )
{
	my_reader = n_reader;
}

void ClassifierRank::setExtractor( LcFeatureExtractor * n_extractor )
{
	my_extractor = n_extractor;
}

void ClassifierRank::setClassifier( LcClassifier * n_classifier)
{
	my_classifier = n_classifier;
}

void ClassifierRank::setFeatureCode( const char * n_code_string )
{
	feature_code_string = n_code_string;
	//my_extractor = new LcFeatureExtractor;
	//my_extractor->set_extractor( n_code_string );
}

void ClassifierRank::crossFrameTest()
{

	int frame_n = my_reader->getN();

	Mat score_record = Mat::zeros(frame_n, frame_n*4, 5);

	string score_record_name;

	{
		score_record_name = my_reader->root + "classifier_output/train_per_frame/" 
			+ my_reader->video_name + "_" + feature_code_string + "_score_record.bin";
	}

	cout << "will write to " << score_record_name << endl;

	for(int test_frame_id = my_reader->f_start, score_record_i = 0;
		test_frame_id  <= my_reader->f_end;
		test_frame_id += my_reader->f_rate, score_record_i++)
	{
		Mat frm, gt;

		Mat desc, lab;

		int statu = my_reader->readNext(frm);

		if(statu!=0) break;

		if(test_frame_id != my_reader->getf())
		{
			cout << "warning! unmatched frame id " << endl;
			return;
		}

		my_reader->getLabel( gt );

		my_extractor->work( frm, desc, gt , lab );


		cout << "==========test on frame " <<
			test_frame_id << " ==============" << endl;


		for(int train_frame_id = my_reader->f_start ,score_record_j = 0;
			train_frame_id <= my_reader->f_end;
			train_frame_id += my_reader->f_rate)
		{
			string classifier_name;

			{
				stringstream _num_str; _num_str << train_frame_id;
				classifier_name = my_reader->root + "classifier_output/train_per_frame/" 
					+ my_reader->video_name + "_" + feature_code_string + "_" +  _num_str.str() ;
			}

			cout << "\ttry to load " << classifier_name << endl;

			//we should very care with that once failed to it will directly exit
			//maybe we can add a previous FileStroage check before that later
			my_classifier->load( classifier_name.c_str() );

			Mat res;
			LcValidator temp_validator = my_classifier->predict( desc, res, lab );

			temp_validator.display();

			score_record.at<float>(score_record_i,score_record_j) = temp_validator.tp; score_record_j++;
			score_record.at<float>(score_record_i,score_record_j) = temp_validator.fp; score_record_j++;
			score_record.at<float>(score_record_i,score_record_j) = temp_validator.fn; score_record_j++;
			score_record.at<float>(score_record_i,score_record_j) = temp_validator.tn; score_record_j++;
		}
	}

	lc::LcMat2Bin( score_record_name.c_str() ,score_record);

	my_reader->reset();
	
}

void ClassifierRank::trainPreFrame()
{
	for(int i = 0 ; i < 1e4; i++)
	{
		Mat frm, gt;

		Mat desc, lab;

		int statu = my_reader->readNext(frm);

		if(statu!=0) break;

		int frame_id = my_reader->getf();

		cout << "==========work with frame " <<
			frame_id << " ==============" << endl;

		my_reader->getLabel( gt );

		my_extractor->work( frm, desc, gt , lab );

		my_classifier->train( desc, lab );

		string classifier_name;

		{
			stringstream _num_str; _num_str << frame_id;
			classifier_name = my_reader->root + "classifier_output/train_per_frame/" 
				+ my_reader->video_name + "_" + feature_code_string + "_" +  _num_str.str() ;
		}

		cout << "save to " << classifier_name << endl;

		my_classifier->save( classifier_name.c_str() );
	}

	my_reader->reset();
}
